package com.example.origin.demo.utils;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.*;
import java.util.function.Supplier;

public class LocalCacheHolder {

    public static final String LOCAL_CACHE_ID = "REQUEST_LOCAL_CACHE_ID";
    public static final String LOCAL_KEY = "REQUEST_LOCAL_KEY";

    //Singleton单例
    private LocalCacheHolder() {
    }

    private static class SingletonHolder {
        private static final Cache<String, Object> INSTANCE = CacheUtil.newLFUCache(100, 10000);
    }

    private static Cache<String, Object> getInstance() {
        return SingletonHolder.INSTANCE;
    }


    public static <T> T get(String key, Supplier<T> supplier) {

        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        String requestId = getRequestId(requestAttributes);
        String name = String.join(":", LOCAL_CACHE_ID, requestId, key);


        getSyncSet(requestAttributes).add(name);

        Cache<String, Object> instance = getInstance();
        return (T) instance.get(name, supplier::get);
    }

    private static Set<String> getSyncSet(RequestAttributes requestAttributes) {
        Object attribute = requestAttributes.getAttribute(LOCAL_CACHE_ID, RequestAttributes.SCOPE_REQUEST);
        Set<String> sets;
        if (attribute == null) {
            synchronized (LocalCacheHolder.class) {
                attribute = requestAttributes.getAttribute(LOCAL_CACHE_ID, RequestAttributes.SCOPE_REQUEST);
                if (attribute == null) {
                    requestAttributes.setAttribute(LOCAL_CACHE_ID, Collections.synchronizedSet(new HashSet<>()), RequestAttributes.SCOPE_REQUEST);
                    registerCallBack(requestAttributes, LOCAL_CACHE_ID);
                    attribute = requestAttributes.getAttribute(LOCAL_CACHE_ID, RequestAttributes.SCOPE_REQUEST);
                }
            }
        }
        sets = (Set<String>) attribute;
        return sets;
    }

    private static String getRequestId(RequestAttributes requestAttributes) {
        Object attribute = requestAttributes.getAttribute(LOCAL_KEY, RequestAttributes.SCOPE_REQUEST);
        if (attribute == null) {
            synchronized (LocalCacheHolder.class) {
                attribute = requestAttributes.getAttribute(LOCAL_KEY, RequestAttributes.SCOPE_REQUEST);
                if (attribute == null) {
                    requestAttributes.setAttribute(LOCAL_KEY, UUID.randomUUID().toString(), RequestAttributes.SCOPE_REQUEST);
                    registerCallBack(requestAttributes, LOCAL_KEY);
                    attribute = requestAttributes.getAttribute(LOCAL_KEY, RequestAttributes.SCOPE_REQUEST);
                }
            }
        }
        return Objects.requireNonNull(attribute).toString();
    }

    private static void registerCallBack(RequestAttributes requestAttributes, String key) {
        requestAttributes.registerDestructionCallback(key, () -> {
            Object attribute = requestAttributes.getAttribute(key, RequestAttributes.SCOPE_REQUEST);
            if (attribute != null) {
                if(attribute instanceof Set){
                    Set<String> sets = (Set<String>) attribute;
                    for (String name : sets) {
                        getInstance().remove(name);
                    }
                }
            }
        }, RequestAttributes.SCOPE_REQUEST);
    }
}
